package cloud.coder.remote.grpc.server;

import cloud.coder.remote.ServerException;
import cloud.coder.remote.config.Config;
import cloud.coder.remote.grpc.health.HealthCheckServiceHandler;
import cloud.coder.remote.grpc.server.auth.AuthenticationInterceptor;
import cloud.coder.remote.util.StringUtil;
import io.grpc.BindableService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;

public class ServiceServer {
    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceServer.class);

    private String host;
    private int port;

    private int maxConcurrentCallsPerConnection = 4;
    private int maxMessageSize = Integer.MAX_VALUE;
    private int threadPoolSize = Runtime.getRuntime().availableProcessors() * 4;
    private int threadPoolQueueSize = 10000;

    private GRPCServer grpcServer;
    private AuthenticationInterceptor authenticationInterceptor;
    private String authStr = Config.AUTHENTICATION;

    public ServiceServer(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void addHandler(BindableService handler) {
        grpcServer.addHandler(handler);
    }

    public void init(int maxConcurrentCallsPerConnection, int maxMessageSize, int threadPoolQueueSize, int threadPoolSize) {
        this.maxConcurrentCallsPerConnection = maxConcurrentCallsPerConnection;
        this.maxMessageSize = maxMessageSize;
        this.threadPoolSize = threadPoolQueueSize;
        this.threadPoolQueueSize = threadPoolSize;
    }

    public void init() throws ServerException {
        grpcServer = new GRPCServer(host, port);
        if (maxMessageSize > 0) {
            grpcServer.setMaxMessageSize(maxMessageSize);
        }
        if (maxConcurrentCallsPerConnection > 0) {
            grpcServer.setMaxConcurrentCallsPerConnection(maxConcurrentCallsPerConnection);
        }
        if (threadPoolQueueSize > 0) {
            grpcServer.setThreadPoolQueueSize(threadPoolQueueSize);
        }
        if (threadPoolSize > 0) {
            grpcServer.setThreadPoolSize(threadPoolQueueSize);
        }

        grpcServer.initialize();

        if (StringUtil.isNotBlank(authStr)) {
            grpcServer.addHandler(new AuthenticationInterceptor(authStr));
        }

        if (Objects.nonNull(grpcServer)) {
            grpcServer.addHandler(new HealthCheckServiceHandler());
        }
        registerHook();
    }

    public void startServer() throws ServerException {
        grpcServer.start();
    }

    private void registerHook() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                // Use stderr here since the logger may have been reset by its JVM shutdown hook.
                LOGGER.info("*** shutting down gRPC server since JVM is shutting down");
                try {
                    ServiceServer.this.grpcServer.close();
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
                LOGGER.info("*** server shut down");
            }
        });
    }

    public void blockUntilShutdown() throws InterruptedException {
        grpcServer.blockUntilShutdown();
    }
}
